{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 题目\n",
    "\n",
    "小时候数学老师的随堂测验，都是老师在黑板上写题目，学生在下边抄，然后再做题目。设计一个程序，模拟学生A和B抄题目做试卷的过程。\n",
    "\n",
    "## 基础版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "学生A抄的试卷以及答案\n",
      "题目1: !+1=?，a.2 b.3 c.4. d.1\n",
      "我选:a\n",
      "题目2: 2+1=?，a.2 b.3 c.4. d.1\n",
      "我选:b\n",
      "题目3: 2+2=?，a.2 b.3 c.4. d.1\n",
      "我选:c\n",
      "学生B抄的试卷以及答案\n",
      "题目1: !+1=?，a.2 b.3 c.4. d.1\n",
      "我选:a\n",
      "题目2: 2+1=?，a.2 b.3 c.4. d.1\n",
      "我选:c\n",
      "题目3: 2+2=?，a.2 b.3 c.4. d.1\n",
      "我选:d\n"
     ]
    }
   ],
   "source": [
    "\n",
    "class TestPaperA():\n",
    "\n",
    "    def test_question_1(self):\n",
    "        print(\"题目1: !+1=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:a\")\n",
    "    \n",
    "    def test_question_2(self):\n",
    "        print(\"题目2: 2+1=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:b\")\n",
    "    \n",
    "    def test_question_3(self):\n",
    "        print(\"题目3: 2+2=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:c\")\n",
    "        \n",
    "class TestPaperB():\n",
    "\n",
    "    def test_question_1(self):\n",
    "        print(\"题目1: !+1=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:a\")\n",
    "    \n",
    "    def test_question_2(self):\n",
    "        print(\"题目2: 2+1=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:c\")\n",
    "    \n",
    "    def test_question_3(self):\n",
    "        print(\"题目3: 2+2=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:d\")\n",
    "        \n",
    "def main():\n",
    "    print(\"学生A抄的试卷以及答案\")\n",
    "    paper_a = TestPaperA()\n",
    "    paper_a.test_question_1()\n",
    "    paper_a.test_question_2()\n",
    "    paper_a.test_question_3()\n",
    "    print(\"学生B抄的试卷以及答案\")\n",
    "    paper_b = TestPaperB()\n",
    "    paper_b.test_question_1()\n",
    "    paper_b.test_question_2()\n",
    "    paper_b.test_question_3()\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "- 学生A和学生B的考卷题目完全一样，重复代码太多\n",
    "- 如果老师修改题目，那所有学生都需要改试卷\n",
    "- 把试卷和答案分离，抽象一个试卷父类，然后学生A和学生B的试卷继承这个父类即可\n",
    "\n",
    "## 改进版本1.0——提炼父类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "学生A抄的试卷以及答案\n",
      "题目1: !+1=?，a.2 b.3 c.4. d.1\n",
      "我选:a\n",
      "题目2: 2+1=?，a.2 b.3 c.4. d.1\n",
      "我选:b\n",
      "题目3: 2+2=?，a.2 b.3 c.4. d.1\n",
      "我选:c\n",
      "学生B抄的试卷以及答案\n",
      "题目1: !+1=?，a.2 b.3 c.4. d.1\n",
      "我选:a\n",
      "题目2: 2+1=?，a.2 b.3 c.4. d.1\n",
      "我选:c\n",
      "题目3: 2+2=?，a.2 b.3 c.4. d.1\n",
      "我选:d\n"
     ]
    }
   ],
   "source": [
    "class TestPaper():\n",
    "    \n",
    "    def test_question_1(self):\n",
    "        print(\"题目1: !+1=?，a.2 b.3 c.4. d.1\")\n",
    "    \n",
    "    def test_question_2(self):\n",
    "        print(\"题目2: 2+1=?，a.2 b.3 c.4. d.1\")\n",
    "    \n",
    "    def test_question_3(self):\n",
    "        print(\"题目3: 2+2=?，a.2 b.3 c.4. d.1\")\n",
    "        \n",
    "class TestPaperA(TestPaper):\n",
    "    \n",
    "    def test_question_1(self):\n",
    "        super().test_question_1()\n",
    "        print(\"我选:a\")\n",
    "    \n",
    "    def test_question_2(self):\n",
    "        super().test_question_2()\n",
    "        print(\"我选:b\")\n",
    "    \n",
    "    def test_question_3(self):\n",
    "        super().test_question_3()\n",
    "        print(\"我选:c\")\n",
    "        \n",
    "    \n",
    "class TestPaperB(TestPaper):\n",
    "    \n",
    "    def test_question_1(self):\n",
    "        super().test_question_1()\n",
    "        print(\"我选:a\")\n",
    "    \n",
    "    def test_question_2(self):\n",
    "        super().test_question_2()\n",
    "        print(\"我选:c\")\n",
    "    \n",
    "    def test_question_3(self):\n",
    "        super().test_question_3()\n",
    "        print(\"我选:d\")\n",
    "\n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "\n",
    "这还是只初步的泛化，两个类中还有类似的代码。比如都有`super().test_question_1()`，还有`print(\"我选:a\")`，除了选项不同，其他都相同。\n",
    "\n",
    "我们既然用了继承，并且认为这个继承是有意义的，那么父类就应该成为子类的模版，所有重复的代码都应该要上升到父类去，而不是让每个子类都去重复。\n",
    "\n",
    "这就需要使用模版方法来处理。\n",
    "\n",
    "当我们要完成在某一细节层次一致的一个过程或一系列步骤，但其个别步骤在更详细的层次上的实现可能不同时，我们通常考虑用模版方法来处理。\n",
    "\n",
    "## 改进版本2.0——提炼细节"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "学生A抄的试卷以及答案\n",
      "题目1: !+1=?，a.2 b.3 c.4. d.1\n",
      "我选:a\n",
      "题目2: 2+1=?，a.2 b.3 c.4. d.1\n",
      "我选:b\n",
      "题目3: 2+2=?，a.2 b.3 c.4. d.1\n",
      "我选:c\n",
      "学生B抄的试卷以及答案\n",
      "题目1: !+1=?，a.2 b.3 c.4. d.1\n",
      "我选:a\n",
      "题目2: 2+1=?，a.2 b.3 c.4. d.1\n",
      "我选:c\n",
      "题目3: 2+2=?，a.2 b.3 c.4. d.1\n",
      "我选:d\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "\n",
    "class TestPaper():\n",
    "    \n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    def test_question_1(self):\n",
    "        print(\"题目1: !+1=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:{}\".format(self.answer_1()))\n",
    "    \n",
    "    @abstractmethod\n",
    "    def answer_1(self):\n",
    "        pass\n",
    "    \n",
    "    def test_question_2(self):\n",
    "        print(\"题目2: 2+1=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:{}\".format(self.answer_2()))\n",
    "    \n",
    "    @abstractmethod\n",
    "    def answer_2(self):\n",
    "        pass\n",
    "    \n",
    "    def test_question_3(self):\n",
    "        print(\"题目3: 2+2=?，a.2 b.3 c.4. d.1\")\n",
    "        print(\"我选:{}\".format(self.answer_3()))\n",
    "\n",
    "    @abstractmethod\n",
    "    def answer_3(self):\n",
    "        pass    \n",
    "    \n",
    "class TestPaperA(TestPaper):\n",
    "    \n",
    "    def answer_1(self):\n",
    "        return \"a\"\n",
    "    \n",
    "    def answer_2(self):\n",
    "        return \"b\"\n",
    "        \n",
    "    def answer_3(self):\n",
    "        return \"c\"\n",
    "        \n",
    "    \n",
    "class TestPaperB(TestPaper):\n",
    "    \n",
    "    def answer_1(self):\n",
    "        return \"a\"\n",
    "    \n",
    "    def answer_2(self):\n",
    "        return \"c\"\n",
    "    \n",
    "    def answer_3(self):\n",
    "        return \"d\"\n",
    "\n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "此时要有更多的学生来答试卷，只是在试卷的模版上填写选择题的选项答案，即可。\n",
    "\n",
    "## 模版方法\n",
    "\n",
    "模版方法，定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤[DP]。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "具体类A的操作1\n",
      "具体类A的操作2\n",
      "具体类B的操作1\n",
      "具体类B的操作2\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "\n",
    "class AbstractClass():\n",
    "    \"\"\"\n",
    "    抽象模版类，定义并实现了一个模版方法，这个模版方法一般是一个具体的算法，\n",
    "    它定义了一个顶级逻辑的骨架，而逻辑的组成步骤在相应的抽象操作中，推迟到\n",
    "    子类实现。当然，顶级逻辑也可能调用一些具体方法。\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def primitive_operation_1(self):\n",
    "        \"\"\"\n",
    "        抽象操作1，放到子类去实现\n",
    "        \"\"\"\n",
    "        pass\n",
    "    \n",
    "    @abstractmethod\n",
    "    def primitive_operation_2(self):\n",
    "        \"\"\"\n",
    "        抽象操作2，放到子类去实现\n",
    "        \"\"\"\n",
    "        pass\n",
    "    \n",
    "    def template_method(self):\n",
    "        \"\"\"\n",
    "        具体模版方法，定义了顶级逻辑骨架\n",
    "        \"\"\"\n",
    "        self.primitive_operation_1()\n",
    "        self.primitive_operation_2()\n",
    "        \n",
    "\n",
    "class ConcreteClassA(AbstractClass):\n",
    "    \"\"\"\n",
    "    具体类A，给出抽象方法的不同实现\n",
    "    \"\"\"\n",
    "    def primitive_operation_1(self):\n",
    "        print(\"具体类A的操作1\")\n",
    "        \n",
    "    def primitive_operation_2(self):\n",
    "        print(\"具体类A的操作2\")\n",
    "        \n",
    "        \n",
    "class ConcreteClassB(AbstractClass):\n",
    "    \"\"\"\n",
    "    具体类B，给出抽象方法的不同实现\n",
    "    \"\"\"\n",
    "    def primitive_operation_1(self):\n",
    "        print(\"具体类B的操作1\")\n",
    "        \n",
    "    def primitive_operation_2(self):\n",
    "        print(\"具体类B的操作2\")\n",
    "        \n",
    "cls = ConcreteClassA()\n",
    "cls.template_method()\n",
    "\n",
    "cls = ConcreteClassB()\n",
    "cls.template_method()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "- 模版方法通过把不变的行为搬移到超类，去除子类中的重复代码来体现它的优势\n",
    "- 模版方法提供了一个很好的代码复用平台\n",
    "- 当不变的和可变的行为在方法的子类实现中混合在一起的时候，不变的行为就会在子类中重复出现。我们通过模版方法模式把这些行为搬移到单一的地方，这样就帮助子类摆脱重复的不变行为的纠缠"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
